//
//  CantripBookmarkController.m
//  ColorListChanger
//
//  Created by James Callender on Mon Jul 21 2003.
//  Copyright (c) 2003 James Callender. All rights reserved.
//

#import "CantripBookmarkController.h"

const NSString *CantripBookmarkStaticType   = @"static";
const NSString *CantripBookmarkBMLType      = @"bigmudlist";
const NSString *CantripBookmarkMenuType     = @"bookmarksmenu";
const NSString *CantripBookmarkAliasType    = @"alias";

#define CantripNumberOfSpecialCollections 2

@implementation CantripBookmarkController

+ (void)initialize
{
    [[NSUserDefaults standardUserDefaults] registerDefaults:
		[NSDictionary dictionaryWithObjectsAndKeys:[NSMutableArray arrayWithObjects:
			[NSDictionary dictionaryWithObjectsAndKeys:
				@"MudConnector Big List", @"bookmarkName",
				@"bigmudlist", @"special",
				[NSMutableArray array], @"children",
				nil],
			[NSDictionary dictionaryWithObjectsAndKeys:
				@"Bookmarks Menu", @"bookmarkName",
				@"bookmarksmenu", @"special",
				[NSMutableArray array], @"children",
				nil],
			nil], @"Bookmarks", nil]];
}

+ (CantripBookmarkController *)sharedInstance
{
    static CantripBookmarkController *sharedInstance = nil;
	
    if (!sharedInstance) {
        sharedInstance = [[CantripBookmarkController alloc] init];
    }
    
    return sharedInstance;
}

- (id)init
{
    if (self = [super initWithWindowNibName:@"CantripBookmarkPanel"]) {
		
    } else {
		NSLog(@"Failed to load CantripBookmarkPanel.nib");
    }
    
    return self;
}

- (void)dealloc
{
    if (self != [self sharedInstance]) {
		[menuMudList dealloc];
		[collectionsList dealloc];
		[bigMudList dealloc];
    }
}

- (void)windowDidLoad
{
    NSTableColumn *column;
    CantripImageAndTextCell *imageTextCell;
    NSToolbar *toolbar = [[[NSToolbar alloc] initWithIdentifier:@"BookmarksToolbarIdentifier"] autorelease];
    NSDictionary *mudconnector;
    
    [[self window] setFrameAutosaveName:@"bookmarksPanel"];
    
    [toolbar setAllowsUserCustomization: YES];
    [toolbar setAutosavesConfiguration: YES];
    [toolbar setDisplayMode:NSToolbarDisplayModeLabelOnly];
    [toolbar setDelegate: self];
    [[self window] setToolbar:toolbar];
    
    [collectionsView setDataSource:self];
    [collectionsView setDelegate:self];
    [collectionsView registerForDraggedTypes:[NSArray arrayWithObjects:@"BookmarkCollectionPBoardType", @"BookmarkNodePBoardType", NSFilenamesPboardType, nil]];
    column = [[collectionsView tableColumns] lastObject];
    imageTextCell = [[[CantripImageAndTextCell alloc] init] autorelease];
    [imageTextCell setEditable:YES];
    [imageTextCell setTarget:self];
    [column setDataCell:imageTextCell];
    
    [bookmarkView setDataSource:self];
    [bookmarkView setDelegate:self];
    [bookmarkView registerForDraggedTypes:[NSArray arrayWithObjects:@"BookmarkNodePBoardType", NSFilenamesPboardType, nil]];
    [bookmarkView setDoubleAction:@selector(nodeAction:)];
    column = [bookmarkView tableColumnWithIdentifier:@"name"];
    imageTextCell = [[[CantripImageAndTextCell alloc] init] autorelease];
    [imageTextCell setEditable:YES];
    [imageTextCell setTarget:self];
    [column setDataCell:imageTextCell];
    
    collectionsList = [[NSMutableArray alloc] initWithContentsOfFile:
		[@"~/Library/Application Support/Cantrip/bookmarks.plist" stringByStandardizingPath]];
    if (!collectionsList) collectionsList = [[NSMutableArray alloc] init];
    [self addParentPointersToArray:collectionsList];
	
    bigMudList = [[NSMutableArray alloc] initWithContentsOfFile:
		[@"~/Library/Application Support/Cantrip/mudconnector.plist" stringByStandardizingPath]];
    if (!bigMudList) bigMudList = [[NSMutableArray alloc] init];
    
    menuMudList = [[NSMutableDictionary alloc] initWithContentsOfFile:
		[@"~/Library/Application Support/Cantrip/menu.plist" stringByStandardizingPath]];
    if (!menuMudList) menuMudList = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
	    [NSMutableArray array], @"children", nil];
    [self addParentPointers:menuMudList];
    
}

#pragma mark Delegate Methods

- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdent willBeInsertedIntoToolbar:(BOOL) willBeInserted 
{
    NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease];
    NSEnumerator *e;
    id module;
    
    if ([itemIdent isEqualToString:@"ReloadMUDListIdentifier"]) {
		[toolbarItem setLabel:@"Reload MUD List"];
		[toolbarItem setToolTip:@"Reload MUD List"];
		[toolbarItem setPaletteLabel:@"Reload MUD List"];
		[toolbarItem setImage:[NSImage imageNamed:@"Connect.psd"]];
		[toolbarItem setTarget:self];
		[toolbarItem setAction:@selector(reloadMUDListAction:)];
    } else if ([itemIdent isEqualToString:@"AddGroupIdentifier"]) {
		[toolbarItem setLabel:@"Add Group"];
		[toolbarItem setToolTip:@"Add Group"];
		[toolbarItem setPaletteLabel:@"Add Group"];
		[toolbarItem setImage:[NSImage imageNamed:@"Connect.psd"]];
		[toolbarItem setTarget:self];
		[toolbarItem setAction:@selector(addGroupAction:)];
    } 
    
    return toolbarItem;
}

- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar 
{
    NSMutableArray *items = [[NSMutableArray alloc] init];    
    
    [items addObjectsFromArray:[NSArray arrayWithObjects:
		@"ReloadMUDListIdentifier",
		NSToolbarFlexibleSpaceItemIdentifier,
		@"AddGroupIdentifier",
		nil]];
    
    return [items autorelease];
}

- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar 
{
    NSMutableArray *items = [[NSMutableArray alloc] init];    
    
    [items addObjectsFromArray:[NSArray arrayWithObjects:
		@"ReloadMUDListIdentifier",
		@"AddGroupIdentifier",
		NSToolbarSeparatorItemIdentifier,
		NSToolbarSpaceItemIdentifier,
		NSToolbarFlexibleSpaceItemIdentifier,
		NSToolbarCustomizeToolbarItemIdentifier,
		nil]];
	
    return [items autorelease];
}

#pragma mark ...

- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
{
    if ([response statusCode] == 200) {
		bytesReceived = 0;
		downloadResponse = response;
		[progressBar setIndeterminate:NO];
		[mainStatusField setStringValue:@"Downloading Mud List"];
		[updatePanel makeKeyAndOrderFront:nil];
		
    } else {
		
    }
}

- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned)length;
{
    long expectedLength=[downloadResponse expectedContentLength];
    bytesReceived += length;
    
    if (expectedLength < 0) {
		[progressBar setIndeterminate:YES];
		[progressBar animate:self];
    } else {
		[progressBar setIndeterminate:NO];
		[progressBar setDoubleValue:(bytesReceived/(float)expectedLength)*100.0];
    }
    
    [subStatusField setStringValue:[self stringForBytes:bytesReceived]];
}

- (void)downloadDidFinish:(NSURLDownload *)download
{
    [progressBar setIndeterminate:YES];
    [subStatusField setStringValue:@"Parsing file..."];
    [progressBar startAnimation:self];
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
    [self buildMUDListFromFile:@"/tmp/cantrip_mudlist.txt"];
    [updatePanel orderOut:nil];
    [bookmarkView reloadData];
    [download release];
    mudlistURLHandle = nil;
}

#pragma mark ...

- (id)menuForTableColumn:(NSTableColumn *)column row:(int)row
{
    id cellData = [bookmarkView itemAtRow:row];
    NSMenu *menu;
    NSMenuItem *item;
    
    menu = [[NSMenu alloc] init];
    
    item = [[NSMenuItem alloc] initWithTitle:@"Open" action:@selector(openMenuAction:) keyEquivalent:@""];
    [item setRepresentedObject:cellData];
    if ([cellData objectForKey:@"children"]) [item setTitle:@"Open All"];
    [menu addItem:[item autorelease]];
    
    [menu addItem:[NSMenuItem separatorItem]];
    
    item = [[NSMenuItem alloc] initWithTitle:@"Show Info" action:@selector(showInfoAction:) keyEquivalent:@""];
    [item setRepresentedObject:cellData];
    if ([cellData objectForKey:@"children"]) [item setAction:NULL];
    [menu addItem:[item autorelease]];
    
    [menu addItem:[NSMenuItem separatorItem]];
    
    item = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteBookmarkAction:) keyEquivalent:@""];
    [item setRepresentedObject:cellData];
    if (![CantripBookmarkAliasType isEqualToString:[cellData objectForKey:@"special"]] && 
		[cellData objectForKey:@"special"]) [item setAction:NULL];
    [menu addItem:[item autorelease]];
	
    return [menu autorelease];
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
    return CantripNumberOfSpecialCollections + [collectionsList count];
}

- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    switch (row) {
		case 0:
			return @"Bookmarks Menu"; break;
		case 1:
			return @"MudConnector"; break;
		default:
			return [[collectionsList objectAtIndex:row - CantripNumberOfSpecialCollections] objectForKey:@"bookmarkName"];
    }
}

- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    [[collectionsList objectAtIndex:row - 2] setObject:object forKey:@"bookmarkName"];
    [self updateViewsAndData];
}

- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    switch (row) {
		case 0:
			[cell setImage:[NSImage imageNamed:@"tiny_menu"]]; break;
		case 1:
			[cell setImage:[NSImage imageNamed:@"mudlist16"]]; break;
		default:
			[cell setImage:[NSImage imageNamed:@"BookmarkSmallGroup"]]; break;
    }
}

- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    switch (row) {
		case 0: case 1: return NO;
		default: return YES;
    }
}

- (void)tableViewSelectionDidChange:(NSNotification *)notification;
{
    [bookmarkView reloadData];
}

- (BOOL)tableView:(NSTableView *)tv writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard
{
    draggedRow = [[rows lastObject] intValue];
    if (draggedRow >= CantripNumberOfSpecialCollections) {
		[pboard declareTypes:[NSArray arrayWithObjects: @"BookmarkCollectionPBoardType", NSFilenamesPboardType, nil] owner:self];
		[pboard setData:[NSData data] forType:@"BookmarkCollectionPBoardType"];
		return YES;
    } 
    
    return NO;
}

- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
{
    NSPasteboard * pboard = [info draggingPasteboard];
    NSEnumerator *e; id n;
	
    if (row == 0 || row >= CantripNumberOfSpecialCollections) {
		
		if ([pboard availableTypeFromArray:[NSArray
		arrayWithObjects:@"BookmarkNodePBoardType", nil]] != nil) {
			
			if (op != NSTableViewDropOn) {
				e = [draggedNodes objectEnumerator];
				while (n = [e nextObject]) {
					if (![n objectForKey:@"children"]) {
						if (row == [tv numberOfRows]) return NSDragOperationNone;
						else { [tv setDropRow:row dropOperation:NSTableViewDropOn]; break; }
					}
				}
			}
			
		}
		
		return NSDragOperationGeneric;
    }
    
    return NSDragOperationNone;
}

// NSTableViewDropOn, NSTableViewDropAbove
- (BOOL)tableView:(NSTableView*)tv acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)op
{
    NSPasteboard * pboard = [info draggingPasteboard];
    NSEnumerator *e; id n;
    unsigned sourceIndex = draggedRow - CantripNumberOfSpecialCollections;
    unsigned targetIndex = row - CantripNumberOfSpecialCollections;
    
    if ([pboard availableTypeFromArray:[NSArray
	    arrayWithObjects:@"BookmarkNodePBoardType", nil]] != nil) {
		
		if (op == NSTableViewDropOn) {
			
		} else {
			if ([info draggingSourceOperationMask] == NSDragOperationCopy) {
				NSMutableArray *newNodes = [NSMutableArray array];
				e = [draggedNodes objectEnumerator];
				while (n = [e nextObject]) [newNodes addObject:[self makeRecursiveCopyOfNode:n]];
				[collectionsList insertObjectsFromArray:newNodes atIndex:targetIndex];
				
			} else {
				e = [draggedNodes reverseObjectEnumerator];
				while (n = [e nextObject]) {
					[[[[n objectForKey:@"parent"] pointerValue] objectForKey:@"children"] removeObjectIdenticalTo:n];
					[collectionsList insertObject:n atIndex:targetIndex];
				}
				
			}
			
			if (row <= [tv selectedRow]) [tv selectRow:([tv selectedRow] + [draggedNodes count]) byExtendingSelection:NO];
		}
    }
    
    if ([pboard availableTypeFromArray:[NSArray
	    arrayWithObjects:@"BookmarkCollectionPBoardType", nil]] != nil) {
		
		if (op == NSTableViewDropOn) {
			if (row == 0) {
				[[menuMudList objectForKey:@"children"] addObject:[collectionsList objectAtIndex:sourceIndex]];
				[collectionsList removeObjectAtIndex:sourceIndex];
			} else {
				id collection = [[collectionsList objectAtIndex:sourceIndex] retain];
				id target = [collectionsList objectAtIndex:targetIndex];
				[collectionsList removeObjectAtIndex:sourceIndex];
				if (targetIndex > sourceIndex) targetIndex--;
				
				[[target objectForKey:@"children"] addObject:[collection autorelease]];
			}
		} else {
			id object = [[collectionsList objectAtIndex:sourceIndex] retain];
			[collectionsList removeObjectAtIndex:sourceIndex];
			if (targetIndex > sourceIndex) targetIndex--;
			
			if (targetIndex >= [collectionsList count]) [collectionsList addObject:[object autorelease]]; 
			else [collectionsList insertObject:[object autorelease] atIndex:targetIndex];
		}
    }
    
    [self updateViewsAndData];
    return YES;
}


- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    if (!item) {
		int row = [collectionsView selectedRow];
		switch(row) {
			case 0: return [[menuMudList objectForKey:@"children"] count]; break;
			case 1: return [bigMudList count]; break;
			default: return [[[collectionsList objectAtIndex:row - 2] objectForKey:@"children"] count];
		}
    } else {
		return [[item objectForKey:@"children"] count];
    }
}

- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
    if (!item) {
		int row = [collectionsView selectedRow];
		switch(row) {
			case 0: return [[menuMudList objectForKey:@"children"] objectAtIndex:index]; break;
			case 1: return [bigMudList objectAtIndex:index]; break;
			default: return [[[collectionsList objectAtIndex:row - 2] objectForKey:@"children"] objectAtIndex:index];
		}
    } else {
		return [[item objectForKey:@"children"] objectAtIndex:index];
    }
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    if (!item) return NO;
    
    return ([item objectForKey:@"children"] != nil); 
}


- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    if (!item) return nil;
    
    if ([[tableColumn identifier] isEqual:@"name"]) {
		return [item objectForKey:@"bookmarkName"];
    } else if ([[tableColumn identifier] isEqual:@"address"]) {
		if ([item objectForKey:@"children"])
			return [NSString stringWithFormat:@"%d Items", [[item objectForKey:@"children"] count]];
		
		if ([item objectForKey:@"bookmarkAddress"])
			return [NSString stringWithFormat:@"telnet://%@:%@", 
				[item objectForKey:@"bookmarkAddress"], [item objectForKey:@"bookmarkPort"]];
		else return @"(none)";
    }
    
    return @"Error";
}

- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
    if ([[tableColumn identifier] isEqual:@"name"]) {
		if ([item objectForKey:@"children"]) [item setObject:object forKey:@"bookmarkName"];
    }
    
    [self updateViewsAndData];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
{    
    if ([collectionsView selectedRow] == 1) return NO;
    if ([[tableColumn identifier] isEqual:@"address"]) return NO;
    
    return YES;
}

- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    if (!item) return;
    [outlineView resetCursorRects];
    [cell setAction:NULL];
    
    if ([[tableColumn identifier] isEqual:@"name"]) {
		[cell setTextColor:[NSColor blackColor]];
		if ([CantripBookmarkMenuType isEqualToString:[item objectForKey:@"special"]])
			[cell setImage:[NSImage imageNamed:@"tiny_menu"]];
		else if ([CantripBookmarkBMLType isEqualToString:[item objectForKey:@"special"]])
			[cell setImage:[NSImage imageNamed:@"mudlist16"]];
		else if ([item objectForKey:@"children"]) 
			[cell setImage:[NSImage imageNamed:@"BookmarkSmallGroup"]];
        else {
			if ([item objectForKey:@"special"] == nil &&
				![[NSFileManager defaultManager] fileExistsAtPath:[item objectForKey:@"documentPath"]])
			{
				[cell setImage:[NSImage imageNamed:@"warningDot"]];
				//[cell setImage:[NSImage imageNamed:@"BookmarkDocumentSmall"]];
				[cell setTextColor:[NSColor redColor]];
			} else if([[item objectForKey:@"special"] isEqualToString:CantripBookmarkAliasType] ||
					  [[item objectForKey:@"special"] isEqualToString:CantripBookmarkStaticType]) {
				[cell setImage:[NSImage imageNamed:@"BookmarkSmall"]];
			} else {
				[cell setImage:[NSImage imageNamed:@"BookmarkDocumentSmall"]];
			}
		}
    } else if ([[tableColumn identifier] isEqual:@"address"]) {
		if ([[item objectForKey:@"special"] isEqualToString:CantripBookmarkBMLType]) {
			//[cell setAction:@selector(nodeAction:)];
			//[[cell controlView] addCursorRect:[outlineView frameOfCellAtColumn:1 row:[outlineView rowForItem:item]] cursor:[NSCursor pointingHandCursor]];
			//[cell setAttributedStringValue:[TMCLink autorelease]];
		} else if ([item objectForKey:@"children"]) [cell setTextColor:[NSColor lightGrayColor]];
        else [cell setTextColor:[NSColor blackColor]];
    }
}

- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray*)items toPasteboard:(NSPasteboard *)pboard
{
    draggedNodes = items;
    
    [pboard declareTypes:[NSArray arrayWithObjects: @"BookmarkNodePBoardType", NSFilenamesPboardType, nil] owner:self];
    [pboard setData:[NSData data] forType:@"BookmarkNodePBoardType"]; 
    
    return YES;
}

- (NSDragOperation)outlineView:(NSOutlineView*)olv validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(int)childIndex
{
    NSPasteboard *pboard = [info draggingPasteboard];
    NSDictionary *targetNode = item;
    BOOL targetNodeIsValid = YES;
    NSDragOperation myOpp = NSDragOperationGeneric;
    NSEnumerator *e; id n;
	
    if ([collectionsView selectedRow] == 1) return NSDragOperationNone;
    if (item == nil && childIndex == -1) return NSDragOperationNone;
    
    if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:@"BookmarkCollectionPBoardType", nil]] != nil) {
		if (draggedRow == [collectionsView selectedRow]) return NSDragOperationNone;
    }
    
    if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]] != nil &&
	    [pboard availableTypeFromArray:[NSArray arrayWithObjects:@"BookmarkNodePBoardType", nil]] == nil) 
		return NSDragOperationGeneric;
	
    if (item == nil) return NSDragOperationGeneric;
    
    if (targetNodeIsValid)
        targetNodeIsValid = ([targetNode objectForKey:@"children"] != nil);
	
    //n = [collectionsList objectAtIndex:0];
    //if (item && CantripParentsOfNode(targetNode, n)) NSLog(@"%d", [CantripParentsOfNode(targetNode, n) count]);
    
    
    if (targetNodeIsValid) {
		NSDictionary *p = targetNode;
		while (p) {
			if ([draggedNodes indexOfObjectIdenticalTo:p] != NSNotFound) targetNodeIsValid = NO;
			p = [[p objectForKey:@"parent"] pointerValue];
		}
    }
    
    [olv setDropItem:targetNode dropChildIndex:childIndex];
    return (targetNodeIsValid ? myOpp : NSDragOperationNone);
}

- (BOOL)outlineView:(NSOutlineView*)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(int)childIndex
{
    NSPasteboard * pboard = [info draggingPasteboard];
    NSMutableArray * itemsToSelect = nil;
    NSMutableArray *children;
    NSDictionary *temp;
    
    if (targetItem == nil) {
		int row = [collectionsView selectedRow];
		switch (row) {
			case 0: children = [menuMudList objectForKey:@"children"]; break;
			default: children = [[collectionsList objectAtIndex:row - CantripNumberOfSpecialCollections] objectForKey:@"children"];
		}
    } else {
		children = [targetItem objectForKey:@"children"];
    }
    
    itemsToSelect = [NSMutableArray arrayWithArray:[outlineView allSelectedItems]];
    [outlineView deselectAll:nil];
    
    if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:@"BookmarkNodePBoardType", nil]] != nil) {
        NSEnumerator *draggedNodesEnum = [draggedNodes objectEnumerator];
        NSMutableDictionary *draggedNode;
        int index = childIndex;
        if (index < 0) index = 0;
        if ([info draggingSourceOperationMask] == NSDragOperationCopy) { // Needs some more work
            NSMutableArray *newDraggedNodes = [NSMutableArray array];
            while (draggedNode = [draggedNodesEnum nextObject])
				[newDraggedNodes addObject:[self makeRecursiveCopyOfNode:draggedNode]];
            [children insertObjectsFromArray:newDraggedNodes atIndex:index]; 
        } else {
            while (draggedNode = [draggedNodesEnum nextObject]) {
                if (targetItem == [[draggedNode objectForKey:@"parent"] pointerValue]) {
					if ([children indexOfObjectIdenticalTo:draggedNode] < childIndex) childIndex--;
				}
				
				[[[[draggedNode objectForKey:@"parent"] pointerValue] objectForKey:@"children"] removeObjectIdenticalTo:draggedNode];
            }
			
			draggedNodesEnum = [draggedNodes objectEnumerator];
			while (draggedNode = [draggedNodesEnum nextObject]) {		
				if (childIndex >= [children count]) [children addObject:draggedNode];
				else [children insertObject:draggedNode atIndex:childIndex];
			}
		}
    } else if ([pboard availableTypeFromArray:[NSArray arrayWithObjects:@"BookmarkCollectionPBoardType", nil]] != nil) {
		id collection = [[collectionsList objectAtIndex:draggedRow - CantripNumberOfSpecialCollections] retain];
		[collectionsList removeObjectAtIndex:draggedRow - CantripNumberOfSpecialCollections];
		[children insertObject:[collection autorelease] atIndex:childIndex];
		if (draggedRow < [collectionsView selectedRow]) [collectionsView selectRow:([collectionsView selectedRow] - 1) byExtendingSelection:NO];
    } else if ([pboard availableTypeFromArray:[NSArray
        arrayWithObjects:NSFilenamesPboardType, nil]] != nil)
    {
        NSEnumerator *e;
        NSString *filePath;
        int index = childIndex;
        if (index < 0) index = 0;
        if (targetItem == nil) index = [children count];
        itemsToSelect = [NSMutableArray arrayWithCapacity:0];
        e = [[pboard propertyListForType:NSFilenamesPboardType] objectEnumerator];
        while (filePath = [e nextObject]) {
            BOOL isDirectory;
            NSMutableDictionary *node;
            NSImage *icon;
            NSString *fileType;
            //if ([self bookmark:filePath existsInNode:]) continue;
            node = [NSMutableDictionary dictionaryWithObjectsAndKeys:
				[[filePath lastPathComponent] stringByDeletingPathExtension], @"bookmarkName",
                @"", @"bookmarkAddress",
                filePath, @"documentPath",
				[NSValue valueWithPointer:targetItem], @"parent",
                nil];
            [[NSFileManager defaultManager] fileExistsAtPath:filePath
												 isDirectory:&isDirectory];
			
            if (isDirectory) {
                [node setObject:[NSMutableArray array] forKey:@"children"];                           
                [children insertObject:node atIndex:index];
                [itemsToSelect addObject:node];
                [self populateBookmarksInDirectory:filePath
										   forNode:node atIndex:0];
            } else {
				NSDictionary *fileData;
				BOOL isWorld = YES;
				@try {
					fileData = [NSUnarchiver unarchiveObjectWithFile:filePath];
				} @catch(id exception) {
					isWorld = NO;
				}
				
				if (isWorld && fileData) {
					if ([[fileData objectForKey:@"WorldData"] objectForKey:@"WorldAddress"])
						[node setObject:[[fileData objectForKey:@"WorldData"] objectForKey:@"WorldAddress"]
								 forKey:@"bookmarkAddress"];
					if ([[fileData objectForKey:@"WorldData"] objectForKey:@"WorldPort"])
						[node setObject:[[fileData objectForKey:@"WorldData"] objectForKey:@"WorldPort"]
								 forKey:@"bookmarkPort"];
					
					[node setObject:filePath forKey:@"documentPath"];
					if (targetItem) [[targetItem objectForKey:@"children"] insertObject:node atIndex:index];
					//else [[bookmarksDictionary objectForKey:@"children"] insertObject:node atIndex:index];
					[itemsToSelect addObject:node];
				}
            }
        }
    }
    
    [self updateViewsAndData];
    [outlineView selectItems: itemsToSelect byExtendingSelection: NO];
	
    return YES;
}

- (NSDragOperation)view:(id)view draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
    if (isLocal) return NSDragOperationEvery;
    else {
		if ([collectionsView selectedRow] != 1) return NSDragOperationDelete;
		else return NSDragOperationNone;
    }
}

- (void)view:(id)view draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
{
    if (operation == NSDragOperationDelete) {
		NSEnumerator *e; id n; NSMutableArray *children;
		NSMutableArray *newList = [NSMutableArray array];
		int row = [collectionsView selectedRow];
		
		if (view == bookmarkView) {
			switch (row) {
				case 0: children = [menuMudList objectForKey:@"children"]; break;
				default: children = [[collectionsList objectAtIndex:row - CantripNumberOfSpecialCollections] objectForKey:@"children"];
			}
			
			[newList addObjectsFromArray:draggedNodes];
			e = [draggedNodes objectEnumerator];
			while ([newList count] && [newList objectAtIndex:0]) {
				n = [newList objectAtIndex:0];
				[children removeObjectIdenticalTo:n];
				if ([n objectForKey:@"children"]) 
					[self removeDescendantsOfNode:n fromArray:newList];
				[newList removeObjectIdenticalTo:n];
			}
		} else if (view == collectionsView) {
			[collectionsList removeObjectAtIndex:draggedRow - CantripNumberOfSpecialCollections];
		}
		
		[self updateViewsAndData];
    }
}

- (void)removeDescendantsOfNode:(NSDictionary *)node fromArray:(NSMutableArray *)list
{
    NSEnumerator *e; id n;
    e = [[node objectForKey:@"children"] objectEnumerator];
    while (n = [e nextObject]) {
		if ([n objectForKey:@"children"]) 
			[self removeDescendantsOfNode:n fromArray:list];
		if ([list containsObject:n]) [list removeObject:n];
    }
}

#pragma mark Interface Actions

- (IBAction)addCollectionAction:(id)sender
{
    [collectionsList addObject:
		[NSMutableDictionary dictionaryWithObjectsAndKeys:
			[NSMutableArray array], @"children",
			@"untitled folder", @"bookmarkName", nil]];
    
    [self updateViewsAndData];
    [collectionsView selectRow:[collectionsView numberOfRows] - 1 byExtendingSelection:NO];
}

- (IBAction)addGroupAction:(id)sender
{
    int row = [collectionsView selectedRow];
    id dict = nil;
    
    if (row >= CantripNumberOfSpecialCollections) 
		dict = [collectionsList objectAtIndex:row - CantripNumberOfSpecialCollections];
    else if (row == 0) dict = menuMudList;
    if (dict) {
		[[dict objectForKey:@"children"] addObject:
			[NSMutableDictionary dictionaryWithObjectsAndKeys:
				[NSValue valueWithPointer:dict], @"parent",
				[NSMutableArray array], @"children",
				@"untitled folder", @"bookmarkName", nil]];
		
		[self updateViewsAndData];
		[bookmarkView selectRow:[bookmarkView numberOfRows] - 1 byExtendingSelection:NO];
    }
}

- (IBAction)reloadMUDListAction:(id)sender
{
    NSURLRequest *theRequest;
    
    if (mudlistURLHandle) { [updatePanel orderFront:nil]; return; }
    
	//http://mudconnector.com/zmud/mudlist.txt
    theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://204.209.44.14/clients/client_data.cgi"]
										 cachePolicy:NSURLRequestUseProtocolCachePolicy
									 timeoutInterval:60.0];
    [theRequest setValue:@"Cantrip_client" forHTTPHeaderField:@"user-agent"];
    
    mudlistURLHandle = [[NSURLDownload alloc] initWithRequest:theRequest
													 delegate:self];
    if (mudlistURLHandle) {
		[mudlistURLHandle setDestination:@"/tmp/cantrip_mudlist.txt" allowOverwrite:YES];
    } else {
		
    }
}

- (IBAction)openMenuAction:(id)sender
{
    NSDictionary *item = [sender representedObject];
    NSArray *children;
    if ([CantripBookmarkMenuType isEqualToString:[item objectForKey:@"special"]]) children = menuMudList;
    else children = [item objectForKey:@"children"];
    
    if (children) {
		NSEnumerator *e; id n; int k = 0; BOOL choice = YES;
		e = [children objectEnumerator];
		while (n = [e nextObject]) {
			if (![n objectForKey:@"children"]) k++;
		}	
		if (k > 20) 
			choice = (NSRunAlertPanel(@"", @"Open all %d worlds?", @"OK", @"Cancel", nil, k) == NSOKButton);
		
		if (choice) {
			e = [children objectEnumerator];
			while (n = [e nextObject]) {
				if (![n objectForKey:@"children"]) {
					[self openBookmark:n];
				}
			}
		}
    } else {
		[self openBookmark:item];
    }
}

- (IBAction)deleteBookmarkAction:(id)sender
{
    [[[[sender representedObject] objectForKey:@"parent"] objectForKey:@"children"] removeObjectIdenticalTo:[sender representedObject]];
    [self updateViewsAndData];
}


- (IBAction)cancelMUDUpdate:(id)sender
{
    [mudlistURLHandle cancel];
    [progressBar setIndeterminate:NO];
    [progressBar setDoubleValue:0.0];
    [updatePanel orderOut:nil];
    [mudlistURLHandle release];
    mudlistURLHandle = nil;
}

- (IBAction)bookmarkMenuAction:(id)sender
{
    [self openBookmark:[sender representedObject]];
}

- (IBAction)nodeAction:(id)sender
{
    [self openBookmark:[bookmarkView selectedItem]];
}

- (IBAction)showPanel:(id)sender
{    
    [self updateViewsAndData];
    [[self window] setTitle:@"Bookmarks"];
    [[self window] makeKeyAndOrderFront:nil];
}

- (IBAction)showInfoAction:(id)sender
{
    id bookmark = [sender representedObject];
    
    
}

#pragma mark Others

- (id)makeRecursiveCopyOfNode:(id)node
{
    NSMutableDictionary *newNode = [[node mutableCopy] autorelease];
    NSEnumerator *e; id n;
    
    if ([node objectForKey:@"children"]) {
		NSMutableArray *newChildren = [NSMutableArray array];
		e = [[node objectForKey:@"children"] objectEnumerator];
		while (n = [e nextObject]) {
			[newChildren addObject:[self makeRecursiveAliasOfNode:n]];
		}
		[newNode setObject:newChildren forKey:@"children"];
    }
    
    return newNode;
}


- (id)makeRecursiveAliasOfNode:(id)node
{
    NSMutableDictionary *newNode = [NSMutableDictionary dictionaryWithDictionary:[[node mutableCopy] autorelease]];
    NSEnumerator *e; id n;
    
    if ([node objectForKey:@"children"]) {
		NSMutableArray *newChildren = [NSMutableArray array];
		e = [[node objectForKey:@"children"] objectEnumerator];
		while (n = [e nextObject]) {
			[newChildren addObject:[self makeRecursiveAliasOfNode:n]];
		}
		[newNode setObject:newChildren forKey:@"children"];
    }
    if ([CantripBookmarkStaticType isEqualToString:[node objectForKey:@"special"]])
		[newNode setObject:CantripBookmarkAliasType forKey:@"special"];
    
    return newNode;
}

- (void)openBookmark:(NSDictionary *)bookmark
{
    if (bookmark && [bookmark objectForKey:@"children"] == nil) {
		id document;
		if ([bookmark objectForKey:@"documentPath"]) {
			document = [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfFile:
													   [bookmark objectForKey:@"documentPath"] display:YES];
			if (!document) {
				NSBeep();
			} else {
				// open references document
			}
		} else {
			document = [[NSDocumentController sharedDocumentController] openUntitledDocumentOfType:@"cworld" display:YES];
			[document setValue:[bookmark objectForKey:@"bookmarkAddress"] forKeyPath:@"data.worldAddress"];
			[document setValue:[NSNumber numberWithInt:[[bookmark objectForKey:@"bookmarkPort"] intValue]] forKeyPath:@"data.worldPort"];
			[[[document windowControllers] objectAtIndex:0] connect];
		}
    } else if ([[bookmark objectForKey:@"special"] isEqualToString:CantripBookmarkBMLType]) {
		[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.mudconnector.com"]];
    }
    
}

- (NSString *)stringForBytes:(long)bytes
{
    if (bytes < 1024) return [NSString stringWithFormat:@"%lld Bytes", bytes];
    if (bytes < 1048576) return [NSString stringWithFormat:@"%.1f KB", (float)bytes / 1024.0];
    return [NSString stringWithFormat:@"%.1f MB", (float)bytes / 1048576.0];
}

- (void)buildMUDListFromFile:(NSString *)file
{
    NSMutableArray *build = [NSMutableArray array];
    NSDictionary *category;
    NSMutableArray *names, *addresses;
    unsigned int c, i;
    NSTask *grepTask1 = [[NSTask alloc] init];
    NSTask *grepTask2 = [[NSTask alloc] init];
    NSPipe *readPipe1 = [NSPipe pipe];
    NSPipe *readPipe2 = [NSPipe pipe];
    NSFileHandle *readHandle1 = [readPipe1 fileHandleForReading];
    NSFileHandle *readHandle2 = [readPipe2 fileHandleForReading];
    NSData *inData;
    NSMutableString *buffer = [NSMutableString stringWithString:@""];
    
    names = [NSMutableArray array];
    addresses = [NSMutableArray array];
    
    [grepTask1 setLaunchPath:@"/usr/bin/grep"];
    [grepTask1 setArguments:[NSArray arrayWithObjects:
									 @"-E", @"Mud[ ]+: [^\n]", file, nil]];
    [grepTask1 setStandardOutput:readPipe1];
    [grepTask1 launch];
    
    
    while ((inData = [readHandle1 availableData]) && [inData length]) {
        unsigned char c;
		unsigned int i;
		for (i = 0; i < [inData length]; i++) {
			c = *((char*)[inData bytes] + i);
			if (c == '\n') {
				[names addObject:[buffer substringFromIndex:14]];
				[buffer deleteCharactersInRange:NSMakeRange(0, [buffer length])];
			} else {
				[buffer appendFormat:@"%c", c];
			}
		}
    }
    //[readHandle closeFile];
    
    //[grepTask release];
    //grepTask = [[NSTask alloc] init];
    [grepTask2 setLaunchPath:@"/usr/bin/grep"];
    [grepTask2 setArguments:[NSArray arrayWithObjects:
								  @"-E", @"Telnet[ ]+: [^\n]", file, nil]];
    [grepTask2 setStandardOutput:readPipe2];
    [grepTask2 launch];
    
    while ((inData = [readHandle2 availableData]) && [inData length]) {
		unsigned char c;
		unsigned int i;
		for (i = 0; i < [inData length]; i++) {
			c = *((char*)[inData bytes] + i);
			if (c == '\n') {
				[addresses addObject:[buffer substringFromIndex:14]];
				[buffer deleteCharactersInRange:NSMakeRange(0, [buffer length])];
			} else {
				[buffer appendFormat:@"%c", c];
			}
		}
    }
    
    [build addObject:[NSDictionary dictionaryWithObjectsAndKeys:
		@"#", @"bookmarkName",
		[NSMutableArray array], @"children", 
		CantripBookmarkStaticType, @"special",
		nil]];
    
    for (c = 65; c < 91; c++) {
		[build addObject:[NSDictionary dictionaryWithObjectsAndKeys:
			[NSString stringWithFormat:@"%c", c], @"bookmarkName",
			[NSMutableArray array], @"children", 
			CantripBookmarkStaticType, @"special",
			nil]];
    }
    
    category = [build objectAtIndex:1];
    
    for (i = 0; i < [names count]; i++) { 
		NSString *mlname = [names objectAtIndex:i];
		NSString *mladdress = [[[addresses objectAtIndex:i] componentsSeparatedByString:@" "] objectAtIndex:0];
		NSString *mlport = [[[addresses objectAtIndex:i] componentsSeparatedByString:@" "] objectAtIndex:1];
		char interestingCharacter = [mlname characterAtIndex:0];
		
		if ([mlname hasPrefix:@"The "]) interestingCharacter = [mlname characterAtIndex:4];
		interestingCharacter = toupper(interestingCharacter);
		
		if (![[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:interestingCharacter])
			category = [build objectAtIndex:0];
		else {
			category = [build objectAtIndex:interestingCharacter - 'A' + 1];
		}
		
		[[category objectForKey:@"children"] addObject:
			[NSMutableDictionary dictionaryWithObjectsAndKeys:
				mlname, @"bookmarkName",
				mladdress, @"bookmarkAddress",
				mlport, @"bookmarkPort",
				CantripBookmarkStaticType, @"special",
				nil]];
    }
    
    [bigMudList autorelease];
    bigMudList = [build retain];
    [bigMudList writeToFile:
	[@"~/Library/Application Support/Cantrip/mudconnector.plist" stringByStandardizingPath] atomically:YES];
}

- (void)populateBookmarksInDirectory:(NSString *)directoryPath forNode:(id)parentNode atIndex:(int)index
{
    NSEnumerator *dir;
    NSString *file;
    BOOL isDirectory;
    dir = [[[NSFileManager defaultManager] directoryContentsAtPath:directoryPath] objectEnumerator];
    
    while (file = [dir nextObject]) {
        NSMutableDictionary *node;
        
        if ([file hasPrefix:@"."]) continue;
		
        node = [NSMutableDictionary dictionaryWithObjectsAndKeys:
            [[file lastPathComponent] stringByDeletingPathExtension], @"bookmarkName",
            @"", @"bookmarkAddress",
			[NSValue valueWithPointer:parentNode], @"parent",
            nil];
		
        [[NSFileManager defaultManager] 
            fileExistsAtPath:[directoryPath stringByAppendingPathComponent:file]
				 isDirectory:&isDirectory];
		
        if (isDirectory) {
            [[parentNode objectForKey:@"children"] insertObject:node atIndex:index];
			[node setObject:[NSMutableArray array] forKey:@"children"];
            [self populateBookmarksInDirectory:
                [directoryPath stringByAppendingPathComponent:file]
									   forNode:node atIndex:0];
        } else {
			NSDictionary *fileData;
			BOOL isWorld = YES;
			
			@try {
				fileData = [NSUnarchiver unarchiveObjectWithFile:[directoryPath stringByAppendingPathComponent:file]];
            } @catch(id exception) {
				isWorld = NO;
			}
			
			if (isWorld && fileData) {
				if ([[fileData objectForKey:@"WorldData"] objectForKey:@"WorldAddress"])
					[node setObject:[[fileData objectForKey:@"WorldData"] objectForKey:@"WorldAddress"]
							 forKey:@"bookmarkAddress"];
				if ([[fileData objectForKey:@"WorldData"] objectForKey:@"WorldPort"])
					[node setObject:[[fileData objectForKey:@"WorldData"] objectForKey:@"WorldPort"]
							 forKey:@"bookmarkPort"];
				[node setObject:[directoryPath stringByAppendingPathComponent:file]
						 forKey:@"documentPath"];
				[[parentNode objectForKey:@"children"] insertObject:node atIndex:index];
			}
        }
    }
}



- (void)addGroup:(NSArray *)group toMenu:(NSMenu *)menu
{
	NSEnumerator *e; id n;
	e = [group objectEnumerator];
	while (n = [e nextObject]) {
		NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[n objectForKey:@"bookmarkName"] action:NULL keyEquivalent:@""];
		if ([n objectForKey:@"children"]) {
			NSMenu *submenu = [[NSMenu alloc] initWithTitle:[n objectForKey:@"bookmarkName"]];
			[self addGroup:[n objectForKey:@"children"] toMenu:submenu];
			[item setImage:[NSImage imageNamed:@"BookmarkSmallGroup"]];
			[item setSubmenu:[submenu autorelease]];
		} else {
			[item setTarget:self];
			[item setAction:@selector(bookmarkMenuAction:)];
			if ([CantripBookmarkAliasType isEqualToString:[n objectForKey:@"special"]])
				[item setImage:[NSImage imageNamed:@"BookmarkSmall"]];
			else  [item setImage:[NSImage imageNamed:@"BookmarkDocumentSmall"]];
			[item setRepresentedObject:n];
		}
		[menu addItem:[item autorelease]];
	}
}

- (void)updateViewsAndData
{
    [self removeParentPointersFromArray:collectionsList];
    [collectionsList writeToFile:[@"~/Library/Application Support/Cantrip/bookmarks.plist" stringByStandardizingPath]
					  atomically:YES];
    [self addParentPointersToArray:collectionsList];
    
    [self removeParentPointers:menuMudList];
    [menuMudList writeToFile:[@"~/Library/Application Support/Cantrip/menu.plist" stringByStandardizingPath]
				  atomically:YES];
    [self addParentPointers:menuMudList];
    
    [self updateBookmarksMenu];
    
    [collectionsView reloadData];
    [bookmarkView reloadData];
}

- (void)updateBookmarksMenu
{
    NSMenu *bookmarksMenu = [[[NSApp mainMenu] itemWithTitle:@"Bookmarks"] submenu];
	
    while ([bookmarksMenu numberOfItems] > 2) {
		[bookmarksMenu removeItemAtIndex:2];
    } 
    [self addGroup:[menuMudList objectForKey:@"children"] toMenu:bookmarksMenu];
}

- (void)removeParentPointersFromArray:(NSArray *)array
{
    NSEnumerator *e; id v;
    e = [array objectEnumerator];
    while (v = [e nextObject]) {
		[self removeParentPointers:v];
    }
}

- (void)addParentPointersToArray:(NSArray *)array
{
    NSEnumerator *e; id v;
    e = [array objectEnumerator];
    while (v = [e nextObject]) {
		[self addParentPointers:v];
    }
}

- (void)addParentPointers:(NSMutableDictionary *)d
{
    NSEnumerator *e; id dn;
    e = [[d objectForKey:@"children"] objectEnumerator];
    while (dn = [e nextObject]) {
		[dn setObject:[NSValue valueWithPointer:d] forKey:@"parent"];
		[self addParentPointers:dn];
    }
}

- (void)removeParentPointers:(NSMutableDictionary *)d
{
    NSEnumerator *e; id dn;
    if ([d objectForKey:@"parent"]) [d removeObjectForKey:@"parent"];
    e = [[d objectForKey:@"children"] objectEnumerator];
    while (dn = [e nextObject]) [self removeParentPointers:dn];
}

@end

id CantripParentOfNode(id node, id root) 
{
    if ([root valueForKey:@"children"]) {
		NSEnumerator *e; id n;
		e = [[root valueForKey:@"children"] objectEnumerator];
		while (n = [e nextObject]) {
			if (node == n) return root;
			else if (CantripParentOfNode(node, n)) return n;
		}
    }
	
    return nil;
}


id CantripParentsOfNode(id node, id root) 
{
    if ([root objectForKey:@"children"]) {
		NSEnumerator *e; id n;
		e = [[root objectForKey:@"children"] objectEnumerator];
		while (n = [e nextObject]) {
			if (node == n) return [NSArray arrayWithObject:root];
			else {
				id n2 = CantripParentOfNode(node, n);
				if (n2 != nil) return [n arrayByAddingObjectsFromArray:n2];
			}
		}
    }
    
    return nil;
}

